using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

namespace IQIGame.Onigao.Framework
{
    /// <summary>
    /// 网络频道。
    /// </summary>
    public sealed partial class NetworkChannel : INetworkChannel, IDisposable
    {
        private const float DefaultHeartBeatInterval = 30f;

        private readonly string m_Name;
        private readonly Queue<BasePacket> m_SendPacketPool;
        private readonly INetworkChannelHelper m_NetworkChannelHelper;
        private NetworkType m_NetworkType;
        private bool m_ResetHeartBeatElapseSecondsWhenReceivePacket;
        private float m_HeartBeatInterval;
        private Socket m_Socket;
        private readonly SendState m_SendState;
        private readonly ReceiveState m_ReceiveState;
        private readonly HeartBeatState m_HeartBeatState;
        private int m_SentPacketCount;
        private int m_ReceivedPacketCount;
        private bool m_Active;
        private bool m_Disposed;

        public event Action<NetworkChannel> NetworkChannelClosed;
        public event Action<NetworkChannel, int> NetworkChannelMissHeartBeat;
        public event Action<NetworkChannel, NetworkErrorCode, SocketError, string, object> NetworkChannelError;
        public event Action<NetworkChannel, object> NetworkChannelCustomError;

        private event Action<BasePacket, INetworkChannel> receivePacketEvent;

        /// <summary>
        /// 初始化网络频道的新实例。
        /// </summary>
        /// <param name="name">网络频道名称。</param>
        /// <param name="networkChannelHelper">网络频道辅助器。</param>
        public NetworkChannel(string name, INetworkChannelHelper networkChannelHelper)
        {
            m_Name = name ?? string.Empty;
            m_SendPacketPool = new Queue<BasePacket>();
            m_NetworkChannelHelper = networkChannelHelper;
            m_NetworkType = NetworkType.Unknown;
            m_ResetHeartBeatElapseSecondsWhenReceivePacket = false;
            m_HeartBeatInterval = DefaultHeartBeatInterval;
            m_Socket = null;
            m_SendState = new SendState();
            m_ReceiveState = new ReceiveState();
            m_HeartBeatState = new HeartBeatState();
            m_SentPacketCount = 0;
            m_ReceivedPacketCount = 0;
            m_Active = false;
            m_Disposed = false;

            NetworkChannelClosed = null;
            NetworkChannelMissHeartBeat = null;
            NetworkChannelError = null;
            NetworkChannelCustomError = null;

            networkChannelHelper.Initialize(this);
        }

        /// <summary>
        /// 获取网络频道名称。
        /// </summary>
        public string Name
        {
            get
            {
                return m_Name;
            }
        }

        ///// <summary>
        ///// 获取网络频道所使用的 Socket。
        ///// </summary>
        //public Socket Socket
        //{
        //    get
        //    {
        //        return m_Socket;
        //    }
        //}

        /// <summary>
        /// 获取是否已连接。
        /// </summary>
        public bool Connected
        {
            get
            {
                if (m_Socket != null)
                {
                    return m_Socket.Connected;
                }

                return false;
            }
        }

        /// <summary>
        /// 获取是否可用
        /// </summary>
        public bool Active
        {
            get
            {
                return m_Active;
            }
        }

        /// <summary>
        /// 获取网络类型。
        /// </summary>
        public NetworkType NetworkType
        {
            get
            {
                return m_NetworkType;
            }
        }

        /// <summary>
        /// 获取要发送的消息包数量。
        /// </summary>
        public int SendPacketCount
        {
            get
            {
                return m_SendPacketPool.Count;
            }
        }

        /// <summary>
        /// 获取累计发送的消息包数量。
        /// </summary>
        public int SentPacketCount
        {
            get
            {
                return m_SentPacketCount;
            }
        }

        /// <summary>
        /// 获取累计已接收的消息包数量。
        /// </summary>
        public int ReceivedPacketCount
        {
            get
            {
                return m_ReceivedPacketCount;
            }
        }

        /// <summary>
        /// 获取或设置当收到消息包时是否重置心跳流逝时间。
        /// </summary>
        public bool ResetHeartBeatElapseSecondsWhenReceivePacket
        {
            get
            {
                return m_ResetHeartBeatElapseSecondsWhenReceivePacket;
            }
            set
            {
                m_ResetHeartBeatElapseSecondsWhenReceivePacket = value;
            }
        }

        /// <summary>
        /// 获取丢失心跳的次数。
        /// </summary>
        public int MissHeartBeatCount
        {
            get
            {
                return m_HeartBeatState.MissHeartBeatCount;
            }
        }

        /// <summary>
        /// 获取或设置心跳间隔时长，以秒为单位。
        /// </summary>
        public float HeartBeatInterval
        {
            get
            {
                return m_HeartBeatInterval;
            }
            set
            {
                m_HeartBeatInterval = value;
            }
        }

        /// <summary>
        /// 获取心跳等待时长，以秒为单位。
        /// </summary>
        public float HeartBeatElapseSeconds
        {
            get
            {
                return m_HeartBeatState.HeartBeatElapseSeconds;
            }
        }

        /// <summary>
        /// 网络频道轮询。
        /// </summary>
        /// <param name="elapseSeconds">逻辑流逝时间，以秒为单位。</param>
        /// <param name="realElapseSeconds">真实流逝时间，以秒为单位。</param>
        public void Update(float elapseSeconds, float realElapseSeconds)
        {
            if (m_Socket == null || !m_Active)
            {
                return;
            }

            ProcessSend();

            if (m_HeartBeatInterval > 0f)
            {
                bool sendHeartBeat = false;
                int missHeartBeatCount = 0;
                lock (m_HeartBeatState)
                {
                    if (m_Socket == null || !m_Active)
                    {
                        return;
                    }

                    m_HeartBeatState.HeartBeatElapseSeconds += realElapseSeconds;
                    if (m_HeartBeatState.HeartBeatElapseSeconds >= m_HeartBeatInterval)
                    {
                        sendHeartBeat = true;
                        missHeartBeatCount = m_HeartBeatState.MissHeartBeatCount;
                        m_HeartBeatState.HeartBeatElapseSeconds = 0f;
                        m_HeartBeatState.MissHeartBeatCount++;
                    }
                }

                if (sendHeartBeat && m_NetworkChannelHelper.SendHeartBeat(this))
                {
                    if (missHeartBeatCount > 0 && NetworkChannelMissHeartBeat != null)
                    {
                        NetworkChannelMissHeartBeat(this, missHeartBeatCount);
                    }
                }
            }
        }

        /// <summary>
        /// 关闭网络频道。
        /// </summary>
        public void Shutdown()
        {
            Close();
            m_NetworkChannelHelper.Shutdown(this);
        }

        /// <summary>
        /// 注册网络消息包处理函数。
        /// </summary>
        /// <param name="handler">要注册的网络消息包处理函数。</param>
        public void RegisterHandler(Action<BasePacket, INetworkChannel> handler)
        {
            if (handler == null)
            {
                throw new FrameworkException("Packet handler is invalid.");
            }

            receivePacketEvent += handler;
        }

        /// <summary>
        /// 连接到远程主机。
        /// </summary>
        /// <param name="ipAddress">远程主机的 IP 地址。</param>
        /// <param name="port">远程主机的端口号。</param>
        /// <param name="userData">用户自定义数据。</param>
        public async Task<bool> Connect(IPAddress ipAddress, int port, object userData)
        {
            if (m_Disposed)
            {
                return false;
            }

            if (m_Socket != null)
            {
                Close();
                m_Socket = null;
            }

            switch (ipAddress.AddressFamily)
            {
                case AddressFamily.InterNetwork:
                    m_NetworkType = NetworkType.IPv4;
                    break;

                case AddressFamily.InterNetworkV6:
                    m_NetworkType = NetworkType.IPv6;
                    break;

                default:
                    string errorMessage = string.Format("Not supported address family '{0}'.", ipAddress.AddressFamily.ToString());
                    if (NetworkChannelError != null)
                    {
                        NetworkChannelError(this, NetworkErrorCode.AddressFamilyError, SocketError.Success, errorMessage, userData);
                        return false;
                    }

                    throw new FrameworkException(errorMessage);
            }

            m_Socket = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            //if (m_Socket == null)
            //{
            //    string errorMessage = "Initialize network channel failure.";
            //    if (NetworkChannelError != null)
            //    {
            //        NetworkChannelError(this, NetworkErrorCode.SocketError, SocketError.Success, errorMessage, userData);
            //        return false;
            //    }

            //    throw new FrameworkException(errorMessage);
            //}

            m_SendState.Reset();
            m_ReceiveState.PrepareForPacketHeader(m_NetworkChannelHelper.PacketHeaderLength);

            try
            {
                Task taskConnect = m_Socket.ConnectAsync(ipAddress, port);
                CancellationTokenSource timeoutCts = new();
                Task taskTimeout = Task.Delay(3000, timeoutCts.Token);
                Task taskComplete = await Task.WhenAny(taskConnect, taskTimeout);
                if(m_Socket == null)
                {
                    LogGame.LogWarning($"Connect error the socket is be set null channelName = {Name}");
                    timeoutCts.Cancel();
                    return false;
                }
                if (taskComplete == taskConnect)
                {
                    //连接完成
                    timeoutCts.Cancel();
                    if (m_Socket.Connected)
                    {
                        OnConnectSuccess();
                        return true;
                    }
                    else
                    {
                        m_Socket = null;
                        m_Active = false;
                        NetworkChannelError?.Invoke(this, NetworkErrorCode.ConnectError, SocketError.Success, "Can't connect to server", userData);
                        return false;
                    }
                }
                else
                {
                    //连接超时
                    m_Socket.Close();
                    m_Socket = null;
                    m_Active = false;
                    NetworkChannelError?.Invoke(this, NetworkErrorCode.ConnectError, SocketError.Success, "Connect server timeout", userData);
                    return false;
                }
            }
            catch (Exception exception)
            {
                m_Socket = null;
                m_Active = false;
                if (NetworkChannelError != null)
                {
                    NetworkChannelError(this, NetworkErrorCode.ConnectError, exception is SocketException socketException ? socketException.SocketErrorCode : SocketError.Success, exception.Message, userData);
                    return false;
                }

                throw;
            }
        }

        private void OnConnectSuccess()
        {
            m_Active = true;

            m_SentPacketCount = 0;
            m_ReceivedPacketCount = 0;

            lock (m_SendPacketPool)
            {
                foreach (BasePacket _pack in m_SendPacketPool)
                {
                    _pack.Active = false;
                }
                m_SendPacketPool.Clear();
            }

            lock (m_HeartBeatState)
            {
                m_HeartBeatState.Reset(true);
            }

            Receive();
        }


        /// <summary>
        /// 关闭连接并释放所有相关资源。
        /// </summary>
        public void Close()
        {
            lock (this)
            {
                if (m_Socket == null)
                {
                    return;
                }

                m_Active = false;

                try
                {
                    m_Socket.Shutdown(SocketShutdown.Both);
                }
                catch
                {
                }
                finally
                {
                    m_Socket.Close();
                    m_Socket = null;

                    NetworkChannelClosed?.Invoke(this);
                }

                m_SentPacketCount = 0;
                m_ReceivedPacketCount = 0;

                lock (m_SendPacketPool)
                {
                    foreach (BasePacket _pack in m_SendPacketPool)
                    {
                        _pack.Active = false;
                    }

                    m_SendPacketPool.Clear();
                }

                lock (m_HeartBeatState)
                {
                    m_HeartBeatState.Reset(true);
                }

                //receivePacketEvent = null;
            }
        }

        /// <summary>
        /// 向远程主机发送消息包。
        /// </summary>
        /// <typeparam name="T">消息包类型。</typeparam>
        /// <param name="packet">要发送的消息包。</param>
        public void Send<T>(T packet) where T : BasePacket
        {
            if (m_Socket == null)
            {
                string errorMessage = "You must connect first.";
                if (NetworkChannelError != null)
                {
                    NetworkChannelError(this, NetworkErrorCode.SendError, SocketError.Success, errorMessage, null);
                    return;
                }

                throw new FrameworkException(errorMessage);
            }

            if (!m_Active)
            {
                string errorMessage = "Socket is not active.";
                if (NetworkChannelError != null)
                {
                    NetworkChannelError(this, NetworkErrorCode.SendError, SocketError.Success, errorMessage, null);
                    return;
                }

                throw new FrameworkException(errorMessage);
            }

            if (packet == null)
            {
                string errorMessage = "Packet is invalid.";
                if (NetworkChannelError != null)
                {
                    NetworkChannelError(this, NetworkErrorCode.SendError, SocketError.Success, errorMessage, null);
                    return;
                }

                throw new FrameworkException(errorMessage);
            }

            lock (m_SendPacketPool)
            {
                packet.Active = true;
                m_SendPacketPool.Enqueue(packet);
            }
        }

        /// <summary>
        /// 释放资源。
        /// </summary>
        public void Dispose()
        {
            receivePacketEvent = null;
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// 释放资源。
        /// </summary>
        /// <param name="disposing">释放资源标记。</param>
        private void Dispose(bool disposing)
        {
            if (m_Disposed)
            {
                return;
            }

            if (disposing)
            {
                Close();
                m_SendState.Dispose();
                m_ReceiveState.Dispose();
            }

            m_Disposed = true;
        }

        private void Send()
        {
            try
            {
                m_Socket.BeginSend(m_SendState.Stream.GetBuffer(), (int)m_SendState.Stream.Position, (int)(m_SendState.Stream.Length - m_SendState.Stream.Position), SocketFlags.None, SendCallback, m_Socket);
            }
            catch (Exception exception)
            {
                m_Active = false;
                if (NetworkChannelError != null)
                {
                    NetworkChannelError(this, NetworkErrorCode.SendError, exception is SocketException socketException ? socketException.SocketErrorCode : SocketError.Success, exception.Message, null);
                    return;
                }

                throw;
            }
        }

        private void Receive()
        {
            try
            {
                m_Socket.BeginReceive(m_ReceiveState.Stream.GetBuffer(), (int)m_ReceiveState.Stream.Position, (int)(m_ReceiveState.Stream.Length - m_ReceiveState.Stream.Position), SocketFlags.None, ReceiveCallback, m_Socket);
            }
            catch (Exception exception)
            {
                m_Active = false;
                if (NetworkChannelError != null)
                {
                    NetworkChannelError(this, NetworkErrorCode.ReceiveError, exception is SocketException socketException ? socketException.SocketErrorCode : SocketError.Success, exception.Message, null);
                    return;
                }

                throw;
            }
        }

        private void ProcessSend()
        {
            if (m_SendState.Stream.Length > 0 || m_SendPacketPool.Count <= 0)
            {
                return;
            }

            while (m_SendPacketPool.Count > 0)
            {
                BasePacket packet = null;
                lock (m_SendPacketPool)
                {
                    packet = m_SendPacketPool.Dequeue();
                }

                bool serializeResult = false;
                try
                {
                    serializeResult = m_NetworkChannelHelper.Serialize(this, packet, m_SendState.Stream);
                    packet.Active = false;
                }
                catch (Exception exception)
                {
                    m_Active = false;
                    if (NetworkChannelError != null)
                    {
                        NetworkChannelError(this, NetworkErrorCode.SerializeError, exception is SocketException socketException ? socketException.SocketErrorCode : SocketError.Success, exception.ToString(), null);
                        return;
                    }

                    throw;
                }

                if (!serializeResult)
                {
                    string errorMessage = "Serialized packet failure.";
                    if (NetworkChannelError != null)
                    {
                        NetworkChannelError(this, NetworkErrorCode.SerializeError, SocketError.Success, errorMessage, null);
                        return;
                    }

                    throw new FrameworkException(errorMessage);
                }
            }

            m_SendState.Stream.Position = 0L;

            Send();
        }

        private bool ProcessPacketHeader()
        {
            try
            {
                object customErrorData = null;
                PacketHeader packetHeader = m_NetworkChannelHelper.DeserializePacketHeader(this, m_ReceiveState.Stream, out customErrorData);

                if (customErrorData != null && NetworkChannelCustomError != null)
                {
                    NetworkChannelCustomError(this, customErrorData);
                }

                if (packetHeader == null)
                {
                    string errorMessage = "Packet header is invalid.";
                    if (NetworkChannelError != null)
                    {
                        NetworkChannelError(this, NetworkErrorCode.DeserializePacketHeaderError, SocketError.Success, errorMessage, null);
                        return false;
                    }

                    throw new FrameworkException(errorMessage);
                }

                m_ReceiveState.PrepareForPacket(packetHeader);
                if (packetHeader.PacketLength <= 0)
                {
                    bool processSuccess = ProcessPacket();
                    m_ReceivedPacketCount++;
                    return processSuccess;
                }
            }
            catch (Exception exception)
            {
                m_Active = false;
                if (NetworkChannelError != null)
                {
                    NetworkChannelError(this, NetworkErrorCode.DeserializePacketHeaderError, exception is SocketException socketException ? socketException.SocketErrorCode : SocketError.Success, exception.ToString(), null);
                    return false;
                }

                throw;
            }

            return true;
        }

        private bool ProcessPacket()
        {
            lock (m_HeartBeatState)
            {
                m_HeartBeatState.Reset(m_ResetHeartBeatElapseSecondsWhenReceivePacket);
            }

            try
            {
                object customErrorData = null;
                BasePacket packet = m_NetworkChannelHelper.DeserializePacket(this, m_ReceiveState.PacketHeader, m_ReceiveState.Stream, out customErrorData);

                if (customErrorData != null && NetworkChannelCustomError != null)
                {
                    NetworkChannelCustomError(this, customErrorData);
                }

                if (packet != null)
                {
                    receivePacketEvent(packet, this);
                }

                m_ReceiveState.PrepareForPacketHeader(m_NetworkChannelHelper.PacketHeaderLength);
            }
            catch (Exception exception)
            {
                m_Active = false;
                if (NetworkChannelError != null)
                {
                    NetworkChannelError(this, NetworkErrorCode.DeserializePacketError, exception is SocketException socketException ? socketException.SocketErrorCode : SocketError.Success, exception.ToString(), null);
                    return false;
                }

                throw;
            }

            return true;
        }

        private void SendCallback(IAsyncResult ar)
        {
            Socket socket = (Socket)ar.AsyncState;
            if (!socket.Connected)
            {
                return;
            }

            int bytesSent = 0;
            try
            {
                bytesSent = socket.EndSend(ar);
            }
            catch (Exception exception)
            {
                m_Active = false;
                if (NetworkChannelError != null)
                {
                    NetworkChannelError(this, NetworkErrorCode.SendError, exception is SocketException socketException ? socketException.SocketErrorCode : SocketError.Success, exception.Message, null);
                    return;
                }

                throw;
            }

            m_SendState.Stream.Position += bytesSent;
            if (m_SendState.Stream.Position < m_SendState.Stream.Length)
            {
                Send();
                return;
            }

            m_SentPacketCount++;
            m_SendState.Reset();
        }

        private void ReceiveCallback(IAsyncResult ar)
        {
            Socket socket = (Socket)ar.AsyncState;
            if (!socket.Connected)
            {
                return;
            }

            int bytesReceived = 0;
            try
            {
                bytesReceived = socket.EndReceive(ar);
            }
            catch (Exception exception)
            {
                m_Active = false;
                if (NetworkChannelError != null)
                {
                    NetworkChannelError(this, NetworkErrorCode.ReceiveError, exception is SocketException socketException ? socketException.SocketErrorCode : SocketError.Success, exception.Message, null);
                    return;
                }

                throw;
            }

            if (bytesReceived <= 0)
            {
                Close();
                return;
            }

            m_ReceiveState.Stream.Position += bytesReceived;
            if (m_ReceiveState.Stream.Position < m_ReceiveState.Stream.Length)
            {
                Receive();
                return;
            }

            m_ReceiveState.Stream.Position = 0L;

            bool processSuccess = false;
            if (m_ReceiveState.PacketHeader != null)
            {
                processSuccess = ProcessPacket();
                m_ReceivedPacketCount++;
            }
            else
            {
                processSuccess = ProcessPacketHeader();
            }

            if (processSuccess)
            {
                Receive();
                return;
            }
        }

        /// <summary>
        /// socket是否可用（派发Close事件后socket会置空）
        /// </summary>
        public bool IsAvailable
        {
            get
            {
                return m_Socket != null;
            }
        }

    }
}
